﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using BlobNet.Base.Collections.List;
using BlobNet.Base.Enums;
using BlobNet.Base.Interfaces;

namespace BlobNet.Base.Collections
{
    [DebuggerDisplay("Count = {Count}")]
    //TODO:quit comment [DebuggerStepThrough]
    [Serializable]
    public class BlobList<TType> : BlobCollection<TType>, IList<TType>
    {
        #region enumerations

        private enum PrivatePersistentListType
        {
            ValueType = 1,
            BlobPart
        }

        #endregion

        #region Constructors

        public BlobList(BlobDomain blobDomain)
            : this(blobDomain, new List<TType>())
        {
        }

        public BlobList(BlobDomain blobDomain, IEnumerable<TType> collection) : base(blobDomain, Guid.NewGuid())
        {
            if (collection == null) throw new ArgumentNullException("collection");

            UpdateBehaviour();

            if ((blobDomain != null && blobDomain.StatusStorage == StatusStorageType.Persistent))
            {
                IList<TType> instance = GetBlobListInstance(collection);
                CurrentCollection = instance;
                StatusStorage = StatusStorageType.Persistent;
            }
            else
            {
                CurrentCollection = new List<TType>(collection);
                StatusStorage = StatusStorageType.Memory;
            }
        }

        public BlobList() : this(null, new List<TType>())
        {
        }

        #endregion

        #region Properties

        private IList<TType> _whenMemoryCollection;
                             //as is not supported by the status holder this is the source when in memory

        private PrivatePersistentListType Behaviour
        {
            get { return GetStatusValue<PrivatePersistentListType>("Behaviour"); }
            set { SetStatusValue("Behaviour", value); }
        }


        protected IList<TType> CurrentCollection
        {
            get
            {
                IList<TType> result;
                if (StatusStorage == StatusStorageType.None)
                    result = null;
                else if (StatusStorage == StatusStorageType.Memory)
                    result = _whenMemoryCollection;
                else
                {

                    switch (Behaviour)
                    {
                        case PrivatePersistentListType.ValueType:
                            result = GetStatusValue<ValueTypePersistentList<TType>>("CurrentCollection");
                            break;
                       
                        case PrivatePersistentListType.BlobPart:
                            result = GetStatusValue<BlobPartsPersistentList<TType>>("CurrentCollection");
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                }
                return result;
            }
            private set
            {
                if (StatusStorage == StatusStorageType.Memory)
                    _whenMemoryCollection = value;
                else
                    SetStatusValue("CurrentCollection", (BlobPart) value);
            }
        }

        

        #endregion

        #region methods

        public BlobList<TType> GetRange(int index, int count)
        {
            BlobList<TType> result;
            if (StatusStorage == StatusStorageType.Memory)
                result = new BlobList<TType>(Domain, ((List<TType>) CurrentCollection).GetRange(index, count));
            else
            {
                switch (Behaviour)
                {
                    case PrivatePersistentListType.ValueType:
                        result = new BlobList<TType>(Domain,
                                                     ((ValueTypePersistentList<TType>) CurrentCollection).GetRange(
                                                         index, count));
                        break;
                    
                    case PrivatePersistentListType.BlobPart:
                        result = new BlobList<TType>(Domain,
                                                     ((BlobPartsPersistentList<TType>) CurrentCollection).GetRange(
                                                         index, count));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            return result;
        }

        private void UpdateBehaviour()
        {
            if (typeof (TType).IsValueType)
            {
                Behaviour = PrivatePersistentListType.ValueType;
            }
            else if (typeof (BlobPart).IsAssignableFrom(typeof (TType)))
            {
                Behaviour = PrivatePersistentListType.BlobPart;
            }
           
            else
            {
                throw new NotSupportedException();
            }
        }

        private IList<TType> GetBlobListInstance(IEnumerable<TType> collection)
        {
            IList<TType> result = null;

            switch (Behaviour)
            {
                case PrivatePersistentListType.ValueType:
                    result = new ValueTypePersistentList<TType>(Domain, EntityId,collection);
                    break;
               
                case PrivatePersistentListType.BlobPart:
                    result = new BlobPartsPersistentList<TType>(Domain, EntityId, collection);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return result;
        }

        

        public void Sort()
        {
            Type listType = CurrentCollection.GetType();
            if (listType == typeof (List<TType>))
            {
                ((List<TType>) CurrentCollection).Sort();
            }
            else if (listType == typeof (PersistentListBase<>))
            {
                ((PersistentListBase<BlobPart>) CurrentCollection).Sort();
            }
            else
            {
                throw new NotImplementedException(string.Format("This method doesn't support the list of type {0}",
                                                                listType));
            }
        }

        #region Implementation of IEnumerable

        public override IEnumerator<TType> GetEnumerator()
        {
            return CurrentCollection.GetEnumerator();
        }

        IEnumerator<TType> IEnumerable<TType>.GetEnumerator()
        {
            return CurrentCollection.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return CurrentCollection.GetEnumerator();
        }

        #endregion

        #region Implementation of ICollection<TType>

        //TODO: implementar Addrange... match..


        public override StatusStorageType StatusStorage
        {
            get { return base.StatusStorage; }
            set
            {
                //if (value == _storageType)
                //    return;

                if (value == StatusStorageType.Persistent && (!CanStoreInDataProvider || !Domain.CanKeepStatusInDb))
                    throw new InvalidOperationException("The blobDomain cannot keep the data in the database");

                var col = CurrentCollection;
                base.StatusStorage = value;
                switch (value)
                {
                    case StatusStorageType.Memory:

                        var temp = new List<TType>();
                        bool isSh = typeof (BlobPart).IsAssignableFrom(typeof (TType));
                        if (CurrentCollection != null)
                        {
                            foreach (TType item in CurrentCollection)
                            {
                                if (isSh && item != null)
                                    (item as BlobPart).StatusStorage = StatusStorageType.Memory;
                                temp.Add(item);
                            }
                        }
                        CurrentCollection = temp;
                        break;
                    case StatusStorageType.Persistent:
                        CurrentCollection = GetBlobListInstance(col);
                        _whenMemoryCollection.Clear();
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("value");
                }
                
            }
        }

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            CurrentCollection.CopyTo((TType[]) array, index);
        }

        public object SyncRoot
        {
            get { return ((ICollection) CurrentCollection).SyncRoot; }
        }

        public bool IsSynchronized
        {
            get { return ((ICollection) CurrentCollection).IsSynchronized; }
        }

        #endregion

        #region IList<TType> Members

        public override int Count
        {
            get { return CurrentCollection.Count; }
        }

        public override void Add(TType item)
        {
            if (typeof (BlobPart).IsAssignableFrom(item.GetType()))
            {
                BlobDomain itemBlobDomain = (item as BlobPart).Domain;
                if (itemBlobDomain != null && Domain != itemBlobDomain)
                    throw new InvalidOperationException("Cannot add items from another BlobDomain");
            }
            CurrentCollection.Add(item);
        }

        public void AddRange(IEnumerable<TType> collection)
        {
            InsertRange(Count, collection);
        }

        //TODO: MOVE OPERATION VALIDATION RULEA FROM CONCRETE LISTS TO HERE WHEN POSSIBLE FOR FASTER EXECUTION
        public void InsertRange(int index, IEnumerable<TType> collection)
        {
            if(StatusStorage==StatusStorageType.Memory)
                ((List<TType>)CurrentCollection).AddRange(collection);
            else
            {
                ((BlobList<TType>)CurrentCollection).AddRange(collection);
            }
        }

        public override void Clear()
        {
            CurrentCollection.Clear();
        }

        public override bool Contains(TType item)
        {
            return CurrentCollection.Contains(item);
        }

        public override void CopyTo(TType[] array, int arrayIndex)
        {
            CurrentCollection.CopyTo(array, arrayIndex);
        }

        public override bool Remove(TType item)
        {
            return CurrentCollection.Remove(item);
        }

        public override bool IsReadOnly
        {
            get { return CurrentCollection.IsReadOnly; }
        }

        #endregion

        public override void Attach(BlobDomain blobDomain)
        {
            if (Domain != blobDomain)
            {
                if (Domain != null)
                    throw new InvalidOperationException("The list is already attached to a blobDomain");

                base.Attach(blobDomain);

                //TODO: improve botch
                List<IPersisteable> result =
                    CurrentCollection.Where(
                        x =>
                        x != null &&
                        x.GetType().GetInterface("BlobNet.Base.Interfaces.IVariantStatusSource") != null).
                        Select(
                            x => (IPersisteable) x).ToList();

                foreach (IPersisteable item in result)
                {
                    item.Attach(Domain);
                    item.StatusStorage = StatusStorage;
                }
            }
        }

        #endregion

        #region Implementation of IList<TType>

        public int IndexOf(TType item)
        {
            return CurrentCollection.IndexOf(item);
        }

        public void Insert(int index, TType item)
        {
            CurrentCollection.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            CurrentCollection.RemoveAt(index);
        }

        public TType this[int index]
        {
            get { return CurrentCollection[index]; }
            set { CurrentCollection[index] = value; }
        }

        #endregion

        #endregion
    }
}